/*------------------------------------------------------------------------------
--                                                                            --
--       This software is confidential and proprietary and may be used        --
--        only as expressly authorized by a licensing agreement from          --
--                                                                            --
--                            Hantro Products Oy.                             --
--                                                                            --
--                   (C) COPYRIGHT 2006 HANTRO PRODUCTS OY                    --
--                            ALL RIGHTS RESERVED                             --
--                                                                            --
--                 The entire notice above must be reproduced                 --
--                  on all copies and should not be removed.                  --
--                                                                            --
--------------------------------------------------------------------------------
*/

#include "vp8header.h"

#include <memory.h>
#include "vp8entropy.h"

static void Segmentation(vp8buffer* buffer, sps* sps, ppss* ppss,
                         entropy* entropy, mbs* mbs);
static void FilterLevelDelta(vp8buffer* buffer, sps* sps);

void VP8FrameHeader(vp8Instance_s* container) {
  refPic* cur_pic = container->picBuffer.cur_pic;
  refPic* refPicList = container->picBuffer.refPicList;
  entropy* entropy = container->entropy;
  sps* sps = &container->sps;                 /* Sequence container */
  pps* pps = container->ppss.pps;
  vp8buffer* buffer = &container->buffer[1];  /* "Frame header" buffer */

  /* Color space and pixel Type (Key frames only) */
  if (cur_pic->i_frame) {
    VP8PutLit(buffer, sps->colorType, 1);
    COMMENT("Frame header, buffer 1, color space type");

    VP8PutLit(buffer, sps->clampType, 1);
    COMMENT("Frame header, buffer 1, clamping type");
  }

  /* Segmentation */
  VP8PutLit(buffer, pps->segmentEnabled, 1);
  COMMENT("Frame header, buffer 1, segmentation flag");
  if (pps->segmentEnabled) {
    Segmentation(buffer, &container->sps, &container->ppss,
                 entropy, &container->mbs);
  }

  VP8PutLit(buffer, sps->filterType, 1);
  COMMENT("Frame header, buffer 1, filter type");

  VP8PutLit(buffer, sps->filterLevel, 6);
  COMMENT("Frame header, buffer 1, filter level");

  VP8PutLit(buffer, sps->filterSharpness, 3);
  COMMENT("Frame header, buffer 1, filter sharpness level");

  /* Loop filter adjustments */
  VP8PutLit(buffer, sps->filterDeltaEnable, 1);
  COMMENT("Frame header, buffer 1, loop filter adjustments");
  if (sps->filterDeltaEnable) {
    /* Filter level delta references reset by key frame */
    if (cur_pic->i_frame) {
      memset(sps->oldRefDelta, 0, sizeof(sps->refDelta));
      memset(sps->oldModeDelta, 0, sizeof(sps->modeDelta));
    }
    FilterLevelDelta(buffer, sps);
  }

  VP8PutLit(buffer, sps->dctPartitions, 2);
  COMMENT("Frame header, buffer 1, token partition");

  VP8PutLit(buffer, container->rateControl.qpHdr, 7);
  COMMENT("Frame header, buffer 1, YacQi quantizer index");

  /* TODO: delta quantization index */
  VP8PutLit(buffer, 0, 1);
  COMMENT("Frame header, buffer 1, YdcDelta flag");
  VP8PutLit(buffer, 0, 1);
  COMMENT("Frame header, buffer 1, Y2dcDelta flag");
  VP8PutLit(buffer, 0, 1);
  COMMENT("Frame header, buffer 1, Y2acDelta flag");
  VP8PutLit(buffer, 0, 1);
  COMMENT("Frame header, buffer 1, UVdcDelta flag");
  VP8PutLit(buffer, 0, 1);
  COMMENT("Frame header, buffer 1, UVacDelta flag");

  /* Update grf and arf buffers and sing bias, see decodframe.c 863.
   * TODO swaping arg->grf and grf->arf in the same time is not working
   * because of bug in the libvpx? */
  if (!cur_pic->i_frame) {
    /* Input picture after reconstruction is set to new grf/arf */
    VP8PutLit(buffer, cur_pic->grf, 1); /* Grf refresh */
    COMMENT("Frame header, buffer 1, grf refresh");
    VP8PutLit(buffer, cur_pic->arf, 1); /* Arf refresh */
    COMMENT("Frame header, buffer 1, arf refresh");

    if (!cur_pic->grf) {
      if (refPicList[0].grf) {
        VP8PutLit(buffer, 1, 2);    /* Ipf -> grf */
        COMMENT("Frame header, buffer 1, ipf -> grf");
      } else if (refPicList[2].grf) {
        VP8PutLit(buffer, 2, 2);    /* Arf -> grf */
        COMMENT("Frame header, buffer 1, arf -> grf");
      } else {
        VP8PutLit(buffer, 0, 2);    /* Not updated */
        COMMENT("Frame header, buffer 1, no update");
      }
    }

    if (!cur_pic->arf) {
      if (refPicList[0].arf) {
        VP8PutLit(buffer, 1, 2);    /* Ipf -> arf */
        COMMENT("Frame header, buffer 1, ipf -> arf");
      } else if (refPicList[1].arf) {
        VP8PutLit(buffer, 2, 2);    /* Grf -> arf */
        COMMENT("Frame header, buffer 1, grf -> arf");
      } else {
        VP8PutLit(buffer, 0, 2);    /* Not updated */
        COMMENT("Frame header, buffer 1, no update");
      }
    }

    /* Sing bias. TODO adaptive sing bias. */
    VP8PutLit(buffer, sps->singBias[1], 1);  /* Grf */
    COMMENT("Frame header, buffer 1, grf sign bias");
    VP8PutLit(buffer, sps->singBias[2], 1);  /* Arf */
    COMMENT("Frame header, buffer 1, arf sign bias");
  }


  /* RefreshEntropyProbs, if 0 -> put default proabilities. If 1
   * use previous frame probabilities */
  VP8PutLit(buffer, sps->refreshEntropy, 1);
  COMMENT("Frame header, buffer 1, Refresh entropy probs flag");

  /* RefreshLastFrame flag. Note that key frame always updates ipf */
  if (!cur_pic->i_frame) {
    VP8PutLit(buffer, cur_pic->ipf, 1);
    COMMENT("Frame header, buffer 1, ipf refresh last frame flag");
  }

  /* Coeff probabilities, TODO: real updates */
  CoeffProb(buffer, entropy->coeffProb, entropy->oldCoeffProb);

  /*  mb_no_coeff_skip . This flag indicates at the frame level if
   *  skipping of macroblocks with no non-zero coefficients is enabled.
   *  If it is set to 0 then prob_skip_false is not read and
   *  mb_skip_coeff is forced to 0 for all macroblocks (see Sections 11.1
   *  and 12.1). TODO  */
  VP8PutLit(buffer, 1, 1);
  COMMENT("Frame header, buffer 1, mb no coeff skip");
  /* Probability used for decoding noCoeff flag, depens above flag TODO*/
  VP8PutLit(buffer, entropy->skipFalseProb, 8);
  COMMENT("Frame header, buffer 1, skip false prob");

  if (cur_pic->i_frame) return;

  /* The rest are inter frame only */

  /* Macroblock is intra predicted probability */
  VP8PutLit(buffer, entropy->intraProb, 8);
  COMMENT("Frame header, buffer 1, intra prob");

  /* Inter is predicted from immediately previous frame probability */
  VP8PutLit(buffer, entropy->lastProb, 8);
  COMMENT("Frame header, buffer 1, last prob");

  /* Inter is predicted from golden frame probability */
  VP8PutLit(buffer, entropy->gfProb, 8);
  COMMENT("Frame header, buffer 1, gf prob");

  /* Intra mode probability updates not supported yet TODO */
  VP8PutLit(buffer, 0, 1);
  COMMENT("Frame header, buffer 1, intra mode prob update");

  /* Intra chroma probability updates not supported yet TODO */
  VP8PutLit(buffer, 0, 1);
  COMMENT("Frame header, buffer 1, intra chroma prob update");

  /* Motion vector probability update not supported yet TOTO real updates */
  MvProb(buffer, entropy->mvProb, entropy->oldMvProb);
}

/*------------------------------------------------------------------------------
    FrameTag
------------------------------------------------------------------------------*/
void VP8FrameTag(vp8Instance_s* container) {
  picBuffer* picBuffer = &container->picBuffer;
  refPic* cur_pic = picBuffer->cur_pic;
  sps* sps = &container->sps;                 /* Sequence container */
  vp8buffer* buffer = &container->buffer[0];  /* Frame tag buffer */
  int32_t tmp;

  /* Frame tag contains (lsb first):
   * 1. A 1-bit frame type (0 for key frames, 1 for inter frames)
   * 2. A 3-bit version number (0 - 3 are defined as 4 different profiles
   * 3. A 1-bit showFrame flag (1 when current frame is display)
   * 4. A 19-bit size of the first data partition in bytes
   * Note that frame tag is written to the stream in little endian mode */

  tmp = ((container->buffer[1].byteCnt) << 5) |
          ((cur_pic->show ? 1 : 0) << 4) |
          (container->sps.profile << 1) |
          (cur_pic->i_frame ? 0 : 1);

  /* Note that frame tag is written _really_ literal to buffer, don't use
   * VP8PutLit() use VP8PutBit() instead */

  VP8PutByte(buffer, tmp & 0xff);
  COMMENT("Frame tag, buffer 0, The first byte");

  VP8PutByte(buffer, (tmp >> 8) & 0xff);
  COMMENT("Frame tag, buffer 0, The second byte");

  VP8PutByte(buffer, (tmp >> 16) & 0xff);
  COMMENT("Frame tag, buffer 0, The third byte");

  if (!cur_pic->i_frame) return;

  /* For key frames this is followed by a further 7 bytes of uncompressed
   * data as follows */
  VP8PutByte(buffer, 0x9d);
  COMMENT("Frame tag, buffer 0, next byte");
  VP8PutByte(buffer, 0x01);
  COMMENT("Frame tag, buffer 0, next byte");
  VP8PutByte(buffer, 0x2a);
  COMMENT("Frame tag, buffer 0, next byte");

  tmp = sps->picWidthInPixel | (sps->horizontalScaling << 14);
  VP8PutByte(buffer, tmp & 0xff);
  COMMENT("Frame tag, buffer 0, next byte");
  VP8PutByte(buffer, tmp >> 8);
  COMMENT("Frame tag, buffer 0, next byte");

  tmp = sps->picHeightInPixel | (sps->verticalScaling << 14);
  VP8PutByte(buffer, tmp & 0xff);
  COMMENT("Frame tag, buffer 0, next byte");
  VP8PutByte(buffer, tmp >> 8);
  COMMENT("Frame tag, buffer 0, next byte");
}

/*------------------------------------------------------------------------------
    Segmentation
------------------------------------------------------------------------------*/
void Segmentation(vp8buffer* buffer, sps* sps, ppss* ppss, entropy* entropy,
                  mbs* mbs) {
  pps* pps = ppss->pps;
  sgm* sgm = &ppss->pps->sgm;     /* New segmentation data */
  int32_t i, tmp;
  bool dataModified = false;

  /* Do we need to updata segmentation data */
  if (memcmp(ppss->qpSgm, pps->qpSgm, sizeof(ppss->qpSgm)))
    dataModified = true;

  if (memcmp(ppss->levelSgm, pps->levelSgm, sizeof(ppss->levelSgm)))
    dataModified = true;

  /* Update segmentation map only if there are no previous map or
   * previous map differs or previous frame did not use segmentation at
   * all. Note also that API set mapModified=true if user changes
   * segmentation map */
  if (!ppss->prevPps) {
    sgm->mapModified = true;
  }

  COMMENT("Frame header, buffer 1, segmentation map modified");
  VP8PutLit(buffer, sgm->mapModified, 1);
  COMMENT("Frame header, buffer 1, segmentation data modified");
  VP8PutLit(buffer, dataModified, 1);

  if (dataModified) {
    COMMENT("Frame header, buffer 1, segmentation data abs");
    /* ABS=1 vs. Deltas=0 */
    VP8PutLit(buffer, 1, 1);

    for (i = 0; i < SGM_CNT; i++) {
      tmp = pps->qpSgm[i];
      VP8PutLit(buffer, 1, 1);
      VP8PutLit(buffer, ABS(tmp), 7);
      VP8PutLit(buffer, tmp < 0, 1);
    }

    for (i = 0; i < SGM_CNT; i++) {
      tmp = pps->levelSgm[i];
      VP8PutLit(buffer, 1, 1);
      VP8PutLit(buffer, ABS(tmp), 6);
      VP8PutLit(buffer, tmp < 0, 1);
    }
  }

  /* Segmentation map probabilities */
  if (sgm->mapModified) {
    int32_t sum1 = sgm->idCnt[0] + sgm->idCnt[1];
    int32_t sum2 = sgm->idCnt[2] + sgm->idCnt[3];

    ASSERT(sum1);
    ASSERT(sum2);

    tmp = 255 * sum1 / (sum1 + sum2);
    entropy->segmentProb[0] = CLIP3(tmp, 1, 255);

    tmp = sum1 ? 255 * sgm->idCnt[0] / sum1 : 255;
    entropy->segmentProb[1] = CLIP3(tmp, 1, 255);

    tmp = sum2 ? 255 * sgm->idCnt[2] / sum2 : 255;
    entropy->segmentProb[2] = CLIP3(tmp, 1, 255);

    for (i = 0; i < 3; i++) {
      if (sgm->idCnt[i] != 0) {
        COMMENT("Frame header, buffer 1, segment prob");
        VP8PutLit(buffer, 1, 1);
        VP8PutLit(buffer, entropy->segmentProb[i], 8);
      } else {
        COMMENT("Frame header, buffer 1, no segment prob");
        VP8PutLit(buffer, 0, 1);
      }
    }
  }

  /* This point new segmentation data is written to the stream, save new
   * values because they are reference values of next frame */
  memcpy(ppss->qpSgm, pps->qpSgm, sizeof(ppss->qpSgm));
  memcpy(ppss->levelSgm, pps->levelSgm, sizeof(ppss->levelSgm));
}

/*------------------------------------------------------------------------------
    FilterLevelDelta
------------------------------------------------------------------------------*/
void FilterLevelDelta(vp8buffer* buffer, sps* sps) {
  int32_t i, tmp;
  int32_t modeUpdate[4];
  int32_t refUpdate[4];
  bool update = false;

  /* Find out what delta values are changed */
  for (i = 0; i < 4; i++) {
    modeUpdate[i] = sps->modeDelta[i] != sps->oldModeDelta[i];
    refUpdate[i] = sps->refDelta[i] != sps->oldRefDelta[i];
    if (modeUpdate[i] || refUpdate[i])
      update = true;
  }

  /* With error resilient mode update the level values for every frame. */
  if (!sps->refreshEntropy)
    update = true;

  /* Do the deltas need to be updated */
  VP8PutLit(buffer, update, 1);
  if (!update) return;

  /* Reference frame mode based deltas */
  for (i = 0; i < 4; i++) {
    VP8PutLit(buffer, refUpdate[i], 1);
    if (refUpdate[i]) {
      tmp = sps->refDelta[i];
      VP8PutLit(buffer, ABS(tmp), 6);    /* Delta */
      VP8PutLit(buffer, tmp < 0, 1); /* Sign */
    }
  }

  /* Macroblock mode based deltas */
  for (i = 0; i < 4; i++) {
    VP8PutLit(buffer, modeUpdate[i], 1);
    if (modeUpdate[i]) {
      tmp = sps->modeDelta[i];
      VP8PutLit(buffer, ABS(tmp), 6);    /* Delta */
      VP8PutLit(buffer, tmp < 0, 1); /* Sign */
    }
  }

  /* Store the new values as reference for next frame */
  memcpy(sps->oldRefDelta, sps->refDelta, sizeof(sps->refDelta));
  memcpy(sps->oldModeDelta, sps->modeDelta, sizeof(sps->modeDelta));
}
